#####################################################################
#
# RFID READER SDK FOR PYTHON
# AUTHOR: zengkai001@qq.com
# DATE: 2025-01-24
#
#####################################################################

import ctypes
import threading
import time

class RFIDReader:
    def __init__(self, library_path):
        """
        初始化 RFID 读写器
        :param library_path: 动态库文件路径，例如 "./x86_64/libGZAE_rfid.so"
        """
        self.library = ctypes.CDLL(library_path)
        self.reader_handle = None  # 用于存储设备句柄
        # self.loop = False  # 用于控制盘点循环
        # self.tags = []  # 存储标签信息

        # 设置相关函数的参数类型和返回类型
        self.library.RDR_SetAcessAntenna.argtypes = [ctypes.c_void_p, ctypes.c_ubyte]
        self.library.RDR_SetAcessAntenna.restype = ctypes.c_int

        self.library.ISO15693_Connect.argtypes = [ctypes.c_void_p, ctypes.c_uint, ctypes.c_ubyte, ctypes.POINTER(ctypes.c_ubyte), ctypes.POINTER(ctypes.c_void_p)]
        self.library.ISO15693_Connect.restype = ctypes.c_int

        self.library.ISO14443A_Connect.argtypes = [ctypes.c_void_p, ctypes.c_uint, ctypes.POINTER(ctypes.c_ubyte), ctypes.c_int, ctypes.POINTER(ctypes.c_void_p)]
        self.library.ISO14443A_Connect.restype = ctypes.c_int

        self.library.RDR_TagDisconnect.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
        self.library.RDR_TagDisconnect.restype = ctypes.c_int

        # EAS function prototypes
        self.library.NXPICODESLI_EableEAS.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
        self.library.NXPICODESLI_EableEAS.restype = ctypes.c_int
        
        self.library.NXPICODESLI_DisableEAS.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
        self.library.NXPICODESLI_DisableEAS.restype = ctypes.c_int
        
        self.library.NXPICODESLI_EASCheck.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.POINTER(ctypes.c_ubyte)]
        self.library.NXPICODESLI_EASCheck.restype = ctypes.c_int

        # System info function prototype
        self.library.ISO15693_GetSystemInfo.argtypes = [
            ctypes.c_void_p,  # reader_handle
            ctypes.c_void_p,  # tag_handle
            ctypes.POINTER(ctypes.c_ubyte),  # uid
            ctypes.POINTER(ctypes.c_ubyte),  # dsfid
            ctypes.POINTER(ctypes.c_ubyte),  # afi
            ctypes.POINTER(ctypes.c_ubyte),  # block_size
            ctypes.POINTER(ctypes.c_ubyte),  # block_count
            ctypes.POINTER(ctypes.c_ubyte)   # ic_ref
        ]
        self.library.ISO15693_GetSystemInfo.restype = ctypes.c_int


    # connStr :RDType=RL8000;CommType=USB;AddrMode=0;SerNum=
    def open_device(self, conn_str):
        """打开RFID设备连接"""
        self.library.RDR_Open.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_void_p)]
        self.library.RDR_Open.restype = ctypes.c_int

        conn_str_encoded = conn_str.encode('utf-8')
        reader_handle = ctypes.c_void_p() # 创建句柄变量
        result = self.library.RDR_Open(ctypes.c_char_p(conn_str_encoded), ctypes.byref(reader_handle))
        if result != 0:
            raise Exception(f"设备打开失败，错误码: {result}")
        self.reader_handle = reader_handle # 存储句柄
        print("设备打开成功")


    def close_device(self):
        """关闭RFID设备连接"""
        if self.reader_handle:
            self.library.RDR_Close.argtypes = [ctypes.c_void_p]
            self.library.RDR_Close.restype = ctypes.c_int
            result = self.library.RDR_Close(self.reader_handle)
            if result != 0:
                raise Exception(f"设备关闭失败，错误码: {result}")
            self.reader_handle = None
            print("设备关闭成功")


    def get_device_info(self, info_type=0):
        """获取设备信息"""
        self.library.RDR_GetReaderInfor.argtypes = [
            ctypes.c_void_p, # RFID_READER_HANDLE
            ctypes.c_ubyte, # BYTE Type
            ctypes.c_char_p, # LPTSTR buffer
            ctypes.POINTER(ctypes.c_uint) # DWORD *nSize
        ]
        self.library.RDR_GetReaderInfor.restype = ctypes.c_int

        buffer_size = ctypes.c_uint(128)
        buffer = ctypes.create_string_buffer(buffer_size.value)

        result = self.library.RDR_GetReaderInfor(
        self.reader_handle,
        ctypes.c_ubyte(info_type),
        buffer,
        ctypes.byref(buffer_size)
        )
        if result != 0:
            raise Exception(f"获取设备信息失败，错误码: {result}")
        return buffer.value.decode('utf-8')


    def start_inventory(self):
        """开始盘点操作"""
        try:
            # 创建盘点参数列表
            self.library.RDR_CreateInvenParamSpecList.restype = ctypes.c_void_p
            dn_inven_param_list = self.library.RDR_CreateInvenParamSpecList()
            print(f"盘点参数列表句柄: {dn_inven_param_list}")
            if not dn_inven_param_list or dn_inven_param_list < 0:
                raise Exception("创建盘点参数列表失败")

            # 初始化 ISO18000-6C 协议参数
            self.library.ISO18000p6C_CreateInvenParam.argtypes = [
                ctypes.c_void_p, ctypes.c_uint, ctypes.c_ubyte, ctypes.c_ubyte, ctypes.c_ubyte, ctypes.c_ubyte
            ]
            self.library.ISO18000p6C_CreateInvenParam.restype = ctypes.c_void_p

            aip_iso18000p6c = self.library.ISO18000p6C_CreateInvenParam(
                dn_inven_param_list,
                0, # ParamType
                0, # ParamSpecType
                0x00, # Session (ISO18000p6C_S0)
                0x00, # Target (ISO18000p6C_TARGET_A)
                0xFF # Q (ISO18000p6C_Dynamic_Q)
            )
            print(f"ISO18000p6C 参数句柄: {aip_iso18000p6c}")
            if not aip_iso18000p6c:
                raise Exception("创建 ISO18000p6C 参数失败")

            # 配置元数据标志（EPC 和 RSSI）
            self.library.ISO18000p6C_SetInvenMetaDataFlags.argtypes = [ctypes.c_void_p, ctypes.c_uint]
            meta_flags = 0x01 | 0x08 # EPC 和 RSSI
            self.library.ISO18000p6C_SetInvenMetaDataFlags(aip_iso18000p6c, meta_flags)
            print(f"设置元数据标志成功: {meta_flags}")

            # 配置天线和其他参数
            antennas = (ctypes.c_ubyte * 1)(0) # 假设天线 ID 为 0
            antenna_count = 1
            option = 0 # 默认选项
            tag_data_list = ctypes.c_void_p()
            tag_count = ctypes.c_uint(0)

            # 调用 RDR_TagInventory
            self.library.RDR_TagInventory.argtypes = [
                ctypes.c_void_p, # RFID_READER_HANDLE
                ctypes.POINTER(ctypes.c_ubyte), # BYTE AntennaID[]
                ctypes.c_uint, # DWORD AntennaCount
                ctypes.c_ubyte, # BYTE Option
                ctypes.c_void_p # RFID_DN_HANDLE hInvenParamSpecList
            ]
            self.library.RDR_TagInventory.restype = ctypes.c_int

            result = self.library.RDR_TagInventory(
                self.reader_handle,
                antennas,
                ctypes.c_uint(antenna_count),
                ctypes.c_ubyte(option),
                dn_inven_param_list
            )

            if result != 0:
                raise Exception(f"盘点失败，错误码: {result}")

            # 调用 RDR_GetTagDataReportCount 获取标签数量
            self.library.RDR_GetTagDataReportCount.argtypes = [ctypes.c_void_p]
            self.library.RDR_GetTagDataReportCount.restype = ctypes.c_uint
            tag_count.value = self.library.RDR_GetTagDataReportCount(self.reader_handle)

            print(f"盘点成功，发现标签数量: {tag_count.value}")

        finally:
            # 销毁参数列表
            if dn_inven_param_list:
                self.library.DNODE_Destroy.argtypes = [ctypes.c_void_p]
                self.library.DNODE_Destroy(dn_inven_param_list)
                print("销毁盘点参数列表")


    def get_tag_info(self):
        """获取当前标签信息"""
        self.library.RDR_GetTagDataReport.argtypes = [ctypes.c_void_p, ctypes.c_uint]
        self.library.RDR_GetTagDataReport.restype = ctypes.c_void_p

        tag_data_list = []
        tag_count = self.library.RDR_GetTagDataReportCount(self.reader_handle)
        print(f"Total tags detected: {tag_count}")
        
        # 使用字典记录每个UID的标签信息和出现次数
        tag_info_dict = {}
        
        for i in range(tag_count):
            tag_data = self.library.RDR_GetTagDataReport(self.reader_handle, i)
            if not tag_data:
                continue
                
            tag_info = self._parse_tag_data(tag_data)
            if not tag_info:
                continue
                
            uid = tag_info['UID']
            if uid in tag_info_dict:
                # 如果UID已存在，增加计数
                tag_info_dict[uid]['count'] += 1
            else:
                # 新UID，初始化计数为1
                tag_info['count'] = 1
                tag_info_dict[uid] = tag_info

        # 将字典转换为列表返回
        tag_data_list = list(tag_info_dict.values())
        print(f"Unique tags found: {len(tag_data_list)}")
        return tag_data_list


    def _parse_tag_data(self, tag_data):
        """解析标签数据"""
        # 定义 ISO15693_ParseTagDataReportEx 的参数和返回类型
        self.library.ISO15693_ParseTagDataReportEx.argtypes = [
            ctypes.c_void_p,  # hTagReport
            ctypes.POINTER(ctypes.c_uint),  # AIPtype
            ctypes.POINTER(ctypes.c_uint),  # TagType
            ctypes.POINTER(ctypes.c_uint),  # AntId
            ctypes.POINTER(ctypes.c_ubyte),  # dsfid
            ctypes.POINTER(ctypes.c_ushort),  # rssi
            ctypes.POINTER(ctypes.c_uint),  # readCount
            ctypes.POINTER(ctypes.c_ubyte)  # UID
        ]
        self.library.ISO15693_ParseTagDataReportEx.restype = ctypes.c_int

        # 准备存储解析结果的变量
        aip_type = ctypes.c_uint()
        tag_type = ctypes.c_uint()
        ant_id = ctypes.c_uint()
        dsfid = ctypes.c_ubyte()
        rssi = ctypes.c_ushort()
        read_count = ctypes.c_uint()
        uid = (ctypes.c_ubyte * 8)()  # UID Max length 8 Bytes

        # 调用 ISO15693_ParseTagDataReportEx 解析标签数据
        result = self.library.ISO15693_ParseTagDataReportEx(
            tag_data,
            ctypes.byref(aip_type),
            ctypes.byref(tag_type),
            ctypes.byref(ant_id),
            ctypes.byref(dsfid),
            ctypes.byref(rssi),
            ctypes.byref(read_count),
            uid
        )

        if result != 0:
            raise Exception(f"解析标签数据失败，错误码: {result}")

        # 将 UID 转换为十六进制字符串
        uid_hex = bytes(uid).hex()

        return {
            'AIPtype': aip_type.value,
            'TagType': tag_type.value,
            'AntId': ant_id.value,
            'DSFID': dsfid.value,
            'RSSI': rssi.value,
            # 'ReadCount': read_count.value,
            'UID': uid_hex
        }


    def read_tag(self, uid, block_start, block_count):
        """
        Read data from RFID tag.

        :param uid: Tag UID
        :param block_start: Start block address
        :param block_count: Number of blocks to read
        :return: Data read from the tag
        """
        # Convert UID to bytes
        uid_bytes = bytes.fromhex(uid)

        # Connect to the tag
        tag_handle = self.connect_to_tag(uid_bytes)
        if tag_handle is None:
            print("Tag not connected")
            return None

        # Calculate the size of the data to read
        read_secsta = False
        nSize = block_count * (5 if read_secsta else 4)
        buffer = (ctypes.c_ubyte * nSize)()
        byts_read_out = ctypes.c_uint(0)

        # Read multiple blocks
        numOfBlks_Readed = ctypes.c_uint(block_count)
        iret = self.library.ISO15693_ReadMultiBlocks(self.reader_handle, tag_handle, read_secsta, block_start, block_count, ctypes.byref(numOfBlks_Readed), buffer, nSize, ctypes.byref(byts_read_out))
        if iret != 0:
            print(f"Error reading blocks: {iret}")
            self.disconnect_tag(tag_handle)
            return None

        # Check if the number of blocks read matches the requested number
        if block_count != numOfBlks_Readed.value:
            print("Mismatch in number of blocks read")
            self.disconnect_tag(tag_handle)
            return None

        # Disconnect from the tag
        self.disconnect_tag(tag_handle)

        return bytes(buffer[:byts_read_out.value])


    def connect_to_tag(self, uid_bytes):
        """
        Connect to the RFID tag.

        :param uid_bytes: Tag UID in bytes
        :return: Tag handle
        """
        tag_handle = ctypes.c_void_p()
        ulen = len(uid_bytes)
        byts = (ctypes.c_ubyte * ulen)(*uid_bytes)
        blen = ctypes.c_int(ulen)
        type = ctypes.c_uint(0)  # 根据实际情况设置类型
        addr_mode = ctypes.c_ubyte(0x01)
        ant_no = ctypes.c_ubyte(1)  # 根据实际情况设置天线编号

        # 设置天线
        iret = self.library.RDR_SetAcessAntenna(self.reader_handle, ant_no)
        if iret != 0:
            print(f"Error setting antenna: {iret}")
            return None

        # 连接标签
        iret = self.library.ISO15693_Connect(self.reader_handle, type, addr_mode, byts, ctypes.byref(tag_handle))
        if iret != 0 or tag_handle.value is None:
            print(f"Error connecting to tag: {iret}")
            self.library.RDR_SetAcessAntenna(self.reader_handle, ant_no)
            return None

        return tag_handle

    def disconnect_tag(self, tag):
        """
        Disconnect from the RFID tag.

        :param tag: Tag handle
        """
        if tag is not None:
            self.library.RDR_TagDisconnect(self.reader_handle, tag)

    def ISO15693_ReadMultiBlocks(self, tag, read_secsta, block_start, block_count, buffer, nSize, byts_read_out):
        """
        Read multiple blocks from the RFID tag.

        :param tag: Tag handle
        :param read_secsta: Read security status
        :param block_start: Start block address
        :param block_count: Number of blocks to read
        :param buffer: Buffer to store the read data
        :param nSize: Size of the buffer
        :param byts_read_out: Number of bytes read
        :return: Error code
        """
        numOfBlks_Readed = ctypes.c_uint(block_count)
        iret = self.library.ISO15693_ReadMultiBlocks(self.reader_handle, tag, read_secsta, block_start, block_count, ctypes.byref(numOfBlks_Readed), buffer, nSize, ctypes.byref(byts_read_out))
        return iret


    def write_tag(self, uid, block_start, block_count, data):
        """
        Write data to RFID tag.

        :param uid: Tag UID (hex string)
        :param block_start: Start block address
        :param block_count: Number of blocks to write
        :param data: Data to write (hex string)
        :return: Result of the write operation
        """
        # Convert UID to bytes
        uid_bytes = bytes.fromhex(uid)

        # Connect to the tag
        tag_handle = self.connect_to_tag(uid_bytes)
        if tag_handle is None:
            print("Tag not connected")
            return None

        # Convert hex string to bytes then to ctypes array
        try:
            data_bytes = bytes.fromhex(data)
            data_array = (ctypes.c_ubyte * len(data_bytes))(*data_bytes)
        except ValueError as e:
            print(f"Invalid hex data: {e}")
            self.disconnect_tag(tag_handle)
            return None

        # Write multiple blocks
        iret = self.library.ISO15693_WriteMultipleBlocks(self.reader_handle, tag_handle, block_start, block_count, data_array, len(data_array))
        if iret != 0:
            print(f"Error writing blocks: {iret}")
            self.disconnect_tag(tag_handle)
            return None

        # Disconnect from the tag
        self.disconnect_tag(tag_handle)

        return iret


    def enable_eas(self, uid):
        """
        Enable EAS (Electronic Article Surveillance) for a tag
        
        :param uid: Tag UID (hex string)
        :return: Result code (0 = success)
        """
        # Convert UID to bytes
        uid_bytes = bytes.fromhex(uid)

        # Connect to the tag
        tag_handle = self.connect_to_tag(uid_bytes)
        if tag_handle is None:
            print("Tag not connected")
            return None

        # Enable EAS
        iret = self.library.NXPICODESLI_EableEAS(self.reader_handle, tag_handle)
        if iret != 0:
            print(f"Error enabling EAS: {iret}")
            self.disconnect_tag(tag_handle)
            return None

        # Disconnect from the tag
        self.disconnect_tag(tag_handle)
        return iret


    def disable_eas(self, uid):
        """
        Disable EAS (Electronic Article Surveillance) for a tag
        
        :param uid: Tag UID (hex string)
        :return: Result code (0 = success)
        """
        # Convert UID to bytes
        uid_bytes = bytes.fromhex(uid)

        # Connect to the tag
        tag_handle = self.connect_to_tag(uid_bytes)
        if tag_handle is None:
            print("Tag not connected")
            return None

        # Disable EAS
        iret = self.library.NXPICODESLI_DisableEAS(self.reader_handle, tag_handle)
        if iret != 0:
            print(f"Error disabling EAS: {iret}")
            self.disconnect_tag(tag_handle)
            return None

        # Disconnect from the tag
        self.disconnect_tag(tag_handle)
        return iret


    def check_eas(self, uid):
        """
        Check EAS (Electronic Article Surveillance) status for a tag
        
        :param uid: Tag UID (hex string)
        :return: EAS status (1 = enabled, 0 = disabled) or None on error
        """
        # Convert UID to bytes
        uid_bytes = bytes.fromhex(uid)

        # Connect to the tag
        tag_handle = self.connect_to_tag(uid_bytes)
        if tag_handle is None:
            print("Tag not connected")
            return None

        # Check EAS status
        eas_status = ctypes.c_ubyte()
        iret = self.library.NXPICODESLI_EASCheck(self.reader_handle, tag_handle, ctypes.byref(eas_status))
        if iret != 0:
            print(f"Error checking EAS status: {iret}")
            self.disconnect_tag(tag_handle)
            return None

        # Disconnect from the tag
        self.disconnect_tag(tag_handle)
        return eas_status.value


    def write_afi(self, uid, afi):
        """
        Write AFI (Application Family Identifier) to tag
        
        :param uid: Tag UID (hex string)
        :param afi: AFI value to write (single byte, 0-255)
        :return: Result code (0 = success)
        """
        # Convert UID to bytes
        uid_bytes = bytes.fromhex(uid)

        # Connect to the tag
        tag_handle = self.connect_to_tag(uid_bytes)
        if tag_handle is None:
            print("Tag not connected")
            return None

        # Write AFI
        afi_byte = ctypes.c_ubyte(afi)
        iret = self.library.ISO15693_WriteAFI(self.reader_handle, tag_handle, afi_byte)
        if iret != 0:
            print(f"Error writing AFI: {iret}")
            self.disconnect_tag(tag_handle)
            return None

        # Disconnect from the tag
        self.disconnect_tag(tag_handle)
        return iret


    def get_tag_status(self, uid):
        """
        Get tag system information including DSFID, AFI, block size, block count and IC reference
        
        :param uid: Tag UID (hex string)
        :return: Dictionary containing system info or None on error
        """
        # Convert UID to bytes
        uid_bytes = bytes.fromhex(uid)

        # Connect to the tag
        tag_handle = self.connect_to_tag(uid_bytes)
        if tag_handle is None:
            print("Tag not connected")
            return None

        # Prepare output variables
        uid_out = (ctypes.c_ubyte * 8)()
        dsfid = ctypes.c_ubyte()
        afi = ctypes.c_ubyte()
        block_size = ctypes.c_ubyte()
        block_count = ctypes.c_ubyte()
        ic_ref = ctypes.c_ubyte()

        # Get system info - parameters must match C++ implementation order
        iret = self.library.ISO15693_GetSystemInfo(self.reader_handle, tag_handle,
                                                 uid_out,
                                                 ctypes.byref(dsfid),
                                                 ctypes.byref(afi),
                                                 ctypes.byref(block_size),
                                                 ctypes.byref(block_count),
                                                 ctypes.byref(ic_ref))
        if iret != 0:
            print(f"Error getting system info: {iret}")
            self.disconnect_tag(tag_handle)
            return None

        # Disconnect from the tag
        self.disconnect_tag(tag_handle)

        return {
            'uid': bytes(uid_out).hex(),
            'dsfid': f"0x{dsfid.value:02x}",
            'afi': f"0x{afi.value:02x}",
            'block_size': block_size.value,
            'block_count': block_count.value,
            'ic_ref': f"0x{ic_ref.value:02x}"
        }
